#include "disk.h"
#include "types.h"
#include "file.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"

#define setbitmap(bitmap, pos) (((bitmap)[(pos)/32]) |= (1L<<((pos)%32)))
#define clearbitmap(bitmap, pos) (((bitmap)[(pos)/32]) &= ~(1L<<((pos)%32)))

//used for printf color
#define NONE                 "\e[0m"
#define GREEN               "\e[1;33m"
#define BLUE                 "\e[0;34m"
#define RED                 "\e[0;35m"

uint8 spbuf[BLKSIZE];
sp_block* sphead = (sp_block*)spbuf;

static uint32 disksize;
static uint32 init_block_map[128] = {VALID32BITS,0x3};// spblk+32inode+1rootblk
static uint32 init_inode_map[32] = {0x1};

char cmd[MAXSTRLEN];
char path_name[MAXSTRLEN];
uint32 path_inode[MAXPATHDEPTH];

void cleanfd(){
    uint8 diskbuf[disksize];
    memset(diskbuf, 0, DEVICE_BLOCK_SIZE);
    for(int i=0;i < disksize/DEVICE_BLOCK_SIZE; i++){
        disk_write_block(i,diskbuf);
    }
}

//read fs blk size data into buf 
void readfsblk(uint32 fs_block_num, uint8* buf){
    disk_read_block(fs_block_num*2,buf);
    disk_read_block(fs_block_num*2+1,buf+DEVICE_BLOCK_SIZE);
}
//read fs blk size data into buf 
void writefsblk(uint32 fs_block_num, uint8* buf){
    disk_write_block(fs_block_num*2,buf);
    disk_write_block(fs_block_num*2+1,buf+DEVICE_BLOCK_SIZE);
}

void initfs(){
    printf(RED "Welcome to simple EXT2 !\n"\
        "This file system is built by LoanCold\n"\
        "Hope that you can have fun here!\n"NONE);
    printf("Open file system ...\n");
    if(open_disk()){
        printf("Open file system failed\n");
        exit(-1);
    }
    disksize = get_disk_size();
    readspblk(spbuf);
    if(sphead->magic_num != MAGICNUM){
        // init super block
        printf("File system not existed or broken, create new one\n");
        printf("Clean disk ...\n");
        cleanfd();
        printf("Clean finished\n");
        printf("Initialize file system ...\n");
        sphead->magic_num = MAGICNUM;
        sphead->free_block_count = MAXFREEBLK;
        sphead->free_inode_count = MAXFREEINODE;
        memcpy(sphead->block_map, init_block_map, sizeof(sphead->block_map));
        memcpy(sphead->inode_map, init_inode_map, sizeof(sphead->inode_map));
        writespblk(spbuf);
        //init root dir
        uint8 fsbuf[BLKSIZE];
        struct inode *node = (struct inode *)fsbuf;
        memset(fsbuf, 0, sizeof(fsbuf));
        node->size = 0;
        node->file_type = ITEM_DIR;
        node->link = 1;
        node->block_point[0] = 33;//first data block
        writefsblk(1,fsbuf);
    }
    printf("File system OPEN\n");
}


void closefs(){
    writefsblk(0, spbuf);
    close_disk();
    printf("File system closed\n");
}


// is_inodemap: 1->inodemap 0->blkmap
// return free inode id or failure -1 

int findfreemap(uint32* map, int is_inodemap){
    int i,j;
    int size;
    if(is_inodemap)
        size = 32;
    else
        size = 128;
    // find free data block
    for(i=0; i<size; i++){
        if(map[i] != VALID32BITS)
            break;
    }
    // no block_map space
    if(i == size)
        return -1;
    // find free bit
    uint32 tmp = map[i];
    for(j=0; j<32; j++){
        if(tmp % 2)
            tmp /=2;
        else
            return 32*i+j;
    }
    return -1;
}


// find invalid item_dir
// @ arg type is the type of find(DIR or FILE)
// @ arg *new_dir points new name
//   (0 indicates no need to set new)
// @ return 0 success -1 failure
int find_invalid(uint32 blkid, int size, uint8 type, char* new_name, int item_inode_id){
    uint8 tmpbuf[BLKSIZE];
    readfsblk(blkid, tmpbuf);
    
    struct dir_item* head = (struct dir_item*) tmpbuf;
    for(; size > 0; head++, size -= sizeof(struct dir_item)){
        if(head->type == type &&  head->valid == 0){
            if(new_name){
                head->valid = 1;
                head->inode_id = item_inode_id;
                strcpy(head->name, new_name);
                writefsblk(blkid, tmpbuf);
            }
            return 0;
        }
    }
    return -1;
}

// find invalid item_dir
struct dir_item* find_invalid_file(void* blk, int size){
    struct dir_item* head = (struct dir_item*) blk;
    for(; size > 0; head++, size -= sizeof(struct dir_item)){
        if(head->type == ITEM_FILE &&  head->valid == 0)
            return head;
    }

    return 0;
}

// update certain inode into disk
void updateinode(uint32 id_inode, struct inode newinode){
    uint8 tmpbuf[BLKSIZE];
    readfsblk(id_inode/INODEPERBLK + 1, tmpbuf);//"+1" means add after spblk
    struct inode *target = (struct inode *)tmpbuf+id_inode;
    *target = newinode;
    writefsblk(id_inode/INODEPERBLK + 1, tmpbuf);
}

struct inode getinode(uint32 id_inode){
    uint8 tmpbuf[BLKSIZE];
    readfsblk(id_inode/INODEPERBLK + 1, tmpbuf);//"+1" means add after spblk
    struct inode *target = (struct inode *)tmpbuf+id_inode;
    return *target;
}

// find free space in given head_inode(dir) 
// for new item(file or dir), and change related bits 
// in head_inode(but do nothing on item_inode!)
// @return inode_id or 0 for failure
uint32 dir_linkitem(uint32 parentid, uint32 item_inode_id, char* name, uint8 type){
    uint8 tmpbuf[BLKSIZE];
    uint8 fsbuf[BLKSIZE];
    struct dir_item *head_dir;
    struct inode head_inode;
    int size;
    int blksize = 0;

    head_inode = getinode(parentid);
    // Find invalid
    // TODO: check if no enough space in block_point
    int i=0;
    size = head_inode.size;
    if(!size)   i++;
    while(size){
        blksize = (size >= BLKSIZE)? BLKSIZE: size;
        if(!(find_invalid(head_inode.block_point[i], blksize, type, name, item_inode_id)))
            break;    // break when find invalid
        i++;
        size -= blksize;
    }
    if(!size){//no invalid block: expand more size
        int dirblk;
        if(blksize == BLKSIZE){// Current block full: new blk for item_dir
            // printf("Current block full: here!\n");
            dirblk = findfreemap(sphead->block_map, 0);
            head_inode.block_point[i] = dirblk;
            blksize=0;
        }
        else// Current block not full: expand current
            dirblk = head_inode.block_point[i-1];
        
        // add new item in head_inode 
        // printf("head_inode->block_point[0] = %d\n",head_inode.block_point[0]);
        // printf("blksize = %d\n",blksize);
        // printf("dirblk = %d\n",dirblk);
        readfsblk(dirblk, tmpbuf);
        head_dir = (struct dir_item *)(tmpbuf + blksize);
        head_dir->inode_id = item_inode_id;
        strcpy(head_dir->name, name);
        head_dir->type = type;
        head_dir->valid = 1;
        writefsblk(dirblk, tmpbuf);
        // printf("here!\n");

        head_inode.size += sizeof(struct dir_item);
    }
    updateinode(parentid, head_inode);
    //update head_inode
}


// new item(dir/file) under certain parent dir inode id
// @return new inode id, 0 for failure
uint32 touchitem(uint32 parentid, char* name, uint8 type){
    int i;
    if(type == ITEM_FILE)
        printf("touch %s ...\n",name);
    if(type == ITEM_DIR)
        printf("mkdir %s ...\n",name);
    
    //find space for new item
    uint32 id_inode = findfreemap(sphead->inode_map, 1);
    uint32 id_blk = findfreemap(sphead->block_map, 0);
    // printf("new id_inode %d\n",id_inode);
    // printf("new id_blk %d\n",id_blk);
    if(id_blk == -1 || id_inode == -1){
        printf("touchitem: failed, no free space\n");
        return 0;
    }
    setbitmap(sphead->inode_map, id_inode);
    setbitmap(sphead->block_map, id_blk);
    sphead->dir_inode_count +=1;
    sphead->free_block_count -=1;
    sphead->free_inode_count -=1;
    writespblk(spbuf);
    
    //new item
    dir_linkitem(parentid, id_inode, name, type);//update dir where new item is
    // printf("after dir_linkitem\n");
    struct inode newinode;
    newinode.size = 0;
    newinode.file_type = type;
    newinode.link = 1;
    newinode.block_point[0] = id_blk;
    updateinode(id_inode, newinode);//update new item
    if(type == ITEM_FILE)
        printf("touch %s succeed\n",name);
    if(type == ITEM_DIR)
        printf("mkdir %s succeed\n",name);
    return id_inode;
}

// dst_inode must be empty and exist
// TODO: if dst is not an empty space
// TODO: if blk is full
void copyfile(uint32 dst_inode_id, uint32 src_inode_id){

    struct inode dst_inode = getinode(dst_inode_id);
    struct inode src_inode = getinode(src_inode_id);
    uint32 dst_idblk;
    uint32 src_idblk;
    uint8 src_buf[BLKSIZE];
    int size = src_inode.size;
    int i=0;
    
    dst_inode.size = src_inode.size;
    while(size>0){
        dst_idblk = findfreemap(sphead->block_map, 0);
        setbitmap(sphead->block_map, dst_idblk);
        src_idblk = src_inode.block_point[i];
        readfsblk(src_idblk, src_buf);
        writefsblk(dst_idblk, src_buf);
        dst_inode.block_point[i++] = dst_idblk;
        size -= BLKSIZE;
    }
    updateinode(dst_inode_id, dst_inode);
}

// find item by name in a certain blk
// @return inode_id, 0 for failure
uint32 finditem_byblk(uint32 idblk, char* name, int size, uint8 type){
    // printf("finditem_byblk\n");s
    uint8 tmpbuf[BLKSIZE];
    readfsblk(idblk, tmpbuf);
    
    struct dir_item* head = (struct dir_item*) tmpbuf;
    for(; size > 0; head++, size -= sizeof(struct dir_item)){
        if((type == ITEM_ALL || head->type == type) &&  head->valid == 1){
            // printf("compare name: headname:%s searchname:%s\n",head->name, name);
            if(!strcmp(head->name,name)){// name equal
                return head->inode_id;
            }
        }
    }
    return 0;
}

// find item (file/dir) under certain directory
// @return item inode id, 0 for failure
uint32 finditem_dir(struct inode *dir, char* name, uint8 type){
    // printf("finditem_dir\n");
    uint32 idinode;
    int blksize;
    int size;
    int i=0;
    size = dir->size;
    // find item
    while(size){
        blksize = (size >= BLKSIZE)? BLKSIZE: size;
        if(idinode = finditem_byblk(dir->block_point[i], name, blksize, type))
            return idinode;
        size -= blksize;
        i++;
    }
    return 0;
}

// find item (file/dir) in certain path
// @return item inode id, 0 for failure
// @return uint32* parent: path parent idinode, 0 for failure
uint32 finditem_path(char* path, uint32* parent, char* name){
    //           i   j
    // path: .../name/...
    uint32 idinode;
    struct inode head_inode;
    int i,j;
    idinode = 0;
    i=1;
    j=1;
    if(path[0] != '/'){
        i--;
        j--;
    }
    while(path[j++]!= '\0'){
        *parent = idinode;
        head_inode = getinode(idinode);
        while(path[j]!= '\0' && path[j] !='/')
            j++;
        memcpy(name, path+i, j-i);
        name[j-i] = '\0';
        idinode = finditem_dir(&head_inode, name, ITEM_ALL);
        // printf("name: %s idnode:%d  size of head:%d \n",name, idinode, head_inode.size);
        i = j+1;
    }
    // printf("finditem_path return idinode:%d\n",idinode);
    return idinode;
}


uint32 finditem_path_simple(char* path){
    uint32 parent;
    char name[MAXSTRLEN];
    return finditem_path(path,&parent,name);
}

void printallitem_byblk(uint32 idblk,int size){
    uint8 tmpbuf[BLKSIZE];
    readfsblk(idblk, tmpbuf);
    
    struct dir_item* head = (struct dir_item*) tmpbuf;
    for(; size > 0; head++, size -= sizeof(struct dir_item)){
        if(head->valid == 0)
            continue;
        if(head->type == ITEM_FILE)
            printf(BLUE "%s \n" NONE, head->name);
        if(head->type == ITEM_DIR)
            printf(GREEN "%s \n" NONE, head->name);
    }
    return;
}

void printallitem(char* path){
    printf(".\n");
    printf("..\n");
    struct inode head_inode;
    uint32 idinode;
    int blksize;
    int size;
    int i=0;
    idinode = finditem_path_simple(path);
    head_inode = getinode(idinode);
    // find item
    size = head_inode.size;
    // printf("root node size:%d\n",size);
    while(size){
        blksize = (size >= BLKSIZE)? BLKSIZE: size;
        printallitem_byblk(head_inode.block_point[i], blksize);
        size -= blksize;
        i++;
    }
}
